可以这样类比来想:think of it like you phone is slowing down after you install too many apps, you need to find out which app is using the resourses and is making you phone slow.
1、we can use chrome devtools, and in the performance tab, and see what is taking the most time.
2、we can use react devtools chrome extension, we can track unneccessary rerenders or infinite loop of rerenders.
3、we can use React.memo and useCallback to making app fast and tracking where is slow.
4、I will check if there are any large amount of data to render at once. usually we render only the visible amount of data, use packages like react.window or react.virtulized.
5、avoid inline functions and objects。
Inline functions and objects are recreated on every render, which can cause child components to re-render unnecessarily — especially if those children depend on referential equality (React.memo, dependency arrays, etc.).
Example problem:
1<Child onClick={() => handleClick()} data={{ foo: 1 }} />Every render:
() => handleClick()is a new function{ foo: 1 }is a new objectSo
Childthinks props changed → re-renders every time, even if nothing actually changed.How to fix it:
①Use
useCallbackfor functionsxxxxxxxxxx31const handleClick = useCallback(() => {2// logic3}, []);②Use
useMemofor objects/arraysxxxxxxxxxx11const data = useMemo(() => ({ foo: 1 }), []);
上面这个说法过时了,avoid inline functions and objects说的太绝对了。但我还是把它保留,因为这个说法很流行。
在现代 React 开发中,“绝对禁止内联函数”被认为是一种过度优化(Over-optimization)。面试时,如果你能辩证地看待这个问题,会显得你更有经验。
以下是针对这个问题的深度解析,以及如何在面试中“专业地反驳”或补充:
- 为什么会有“避免内联函数”的说法?
这个观点的核心担忧是 引用相等性(Reference Equality):
- 在 JavaScript 中,
() => {} === () => {}是false。- 如果父组件重新渲染,内联函数会被重新创建,导致传给子组件的 Props 引用发生变化。
- 如果子组件使用了
React.memo,它会误以为数据变了,从而触发重新渲染。
- 为什么这种说法“不准确”?
- 原生标签无所谓:对于
<button>、<div>、<input>这些原生标签,创建内联函数的开销极小,且它们本身不具备memo逻辑,内联函数不会导致任何额外的性能损失。- React 渲染非常快:在大多数中小型组件树中,重新渲染的开销远小于过度使用
useCallback带来的代码维护成本。- useCallback 也有开销:
useCallback本身也要执行逻辑、定义函数并维护依赖数组。如果盲目使用,反而会让代码变得臃肿且性能提升微乎其微。
- 面试时的“标准标准回答” (The "Senior" Answer)
当面试官提到“避免内联函数”时,你可以这样回答:
中文逻辑:
“‘避免内联函数’这个原则在 React 中需要视情况而定。
它的主要目的是为了保持引用稳定。如果一个内联函数被传递给了包裹了
React.memo的子组件,或者作为useEffect的依赖项,那么内联函数确实会导致不必要的重绘或 Effect 的重复执行。但是,对于原生 HTML 标签,或者没有性能瓶颈的普通组件,内联函数是完全可以接受的。过度使用
useCallback反而会增加代码复杂度。所以我的原则是:先编写简洁的内联代码,只有在 Profiler 检测到性能瓶颈,或者需要保持引用一致性时,才去使用
useCallback优化。”
- 英文面试版本 (Pro Interview Version)
"The advice to avoid inline functions is not an absolute rule; it's about managing reference stability.
The real issue isn't the creation of the function itself—React handles that very efficiently. The issue is that a new function reference can break memoization in child components (using
React.memo) or trigger unnecessaryuseEffectcalls.If I'm passing a function to a native element like a
<button>, an inline function is perfectly fine. However, if I'm passing it to a heavy, memoized component, I would wrap it inuseCallbackto ensure the child doesn't re-render unless its actual logic changes.My philosophy is: Don't prematurely optimize. I prefer clean, readable code with inline functions first, and only optimize when the Profiler shows a real performance bottleneck."
上面的步骤要理解,然后简要的答案先说出来:
When optimizing React performance, I follow a systematic approach: Measure, Identify, and then Refine.
First, I use the React DevTools Profiler to identify 'hot' components—those that render too frequently or take too long.
A common issue I look for is unnecessary re-renders caused by broken memoization. I check if stable references are being lost because inline functions or objects are being passed as props to components wrapped in
React.memo. In these specific cases, I useuseCallbackoruseMemoto stabilize those references and ensure memoization actually works.However, I don't just wrap everything in hooks. I also look for architectural smells, such as state being 'lifted' too high. I often optimize by pushing state down to leaf components or using component composition to isolate expensive branches from parent updates.
Finally, for UI-level bottlenecks, I implement Windowing for large lists or Code Splitting to reduce the main thread load. My goal is always to solve the root cause with the simplest effective solution.
面试加分点:
明确了“什么时候”优化内联函数:
memo 时,内联函数才是问题。这非常加分!)引入了“状态下放” (Pushing State Down):
useMemo 更优雅。提到它说明你理解“组合优于配置”。使用了专业词汇:
闭环逻辑:
The general idea is:If the user is already authenticated, redirect them away from the login page.
You check the auth state, and if the user is logged in, redirect them:
x1import { Navigate } from "react-router-dom";2import { useAuth } from "./authContext";34function LoginPage() {5 const { user } = useAuth();67 if (user) {8 return <Navigate to="/dashboard" replace />;9 }1011 return <LoginForm />;12}1314export default LoginPage;Explanation:
user exists → redirect to dashboardThis ensures the login page is never visible to authenticated users.
Sometimes I create a wrapper:
xxxxxxxxxx51function PublicOnlyRoute({ children }) {2 const { user } = useAuth();34 return user ? <Navigate to="/dashboard" replace /> : children;5}Then wrap the route:
xxxxxxxxxx81<Route2 path="/login"3 element={4 <PublicOnlyRoute>5 <LoginPage />6 </PublicOnlyRoute>7 }8/>If the backend renders the page, the server can also redirect if the session exists.
理解上面的内容后,简要回答:
I prevent the login page from being accessible after login by checking whether the user is authenticated. If the user is already logged in, I immediately redirect them to a protected page like the dashboard. In React Router, this usually means returning a
<Navigate>component whenuserexists. This ensures the login page is only visible to unauthenticated users.
Users should never see raw server errors like “500 Internal Server Error”.
Example:
This keeps the UI clean and understandable.
In React components or services:
xxxxxxxxxx71try {2 const res = await fetch("/api/data");3 if (!res.ok) throw new Error("Request failed");4 return await res.json();5} catch (err) {6 setError(err.message);7}Then the UI reacts to that error state.
For example:
xxxxxxxxxx61{error && (2 <div>3 <p>{error}</p>4 <button onClick={retry}>Try Again</button>5 </div>6)}I often show different messages depending on the error:
This makes the UI feel professional and predictable.
React Query makes this much easier:
xxxxxxxxxx71useQuery({2 queryKey: ["data"],3 queryFn: fetchData,4 onError: (err) => {5 toast.error(err.message);6 }7});React Query automatically gives:
error stateisError booleanSo the UI can handle errors more gracefully.
理解上面之后,简要的回答:
I handle API errors by wrapping network calls in
try/catchor using libraries like React Query / Axios, capturing both HTTP-error responses and network exceptions. I store the error state (e.g. usinguseStateor React Query’serror/isErrorflags), and show a user-friendly fallback UI — such as an error message, a toast/notification, or a retry button wrapped in a conditional render. For critical component errors I can also use a global error boundary (via ErrorBoundary) to catch rendering or lifecycle exceptions so the whole UI doesn’t break. For HTTP status codes I differentiate behaviors — e.g. 401 leads to redirect to login, 403 shows “access denied”, 400 shows validation feedback. I may also implement retry logic or exponential-backoff on failures (especially for transient network errors) and log errors (to console or a logging service) for debugging.
useEffect dependency arrayOne of the most common causes is a missing or incorrect dependency array:
❌ Bad (fetch runs every render)
xxxxxxxxxx31useEffect(() => {2 fetchData();3});✅ Good (runs only once on mount)
xxxxxxxxxx31useEffect(() => {2 fetchData();3}, []);Or if dependencies are required, ensure they don’t change on every render.
useCallbackIf the fetch function is declared inline and passed to children or used inside effects, it may be recreated every render, causing repeated calls.
❌ Bad
xxxxxxxxxx41useEffect(() => {2 fetchData();3}, [fetchData]);4const fetchData = () => { }; // new function per render✅ Good
xxxxxxxxxx31const fetchData = useCallback(() => {2 // call API3}, []);useMemoIf parameters for your fetch are created inline (like an object), the reference changes every time:
❌
xxxxxxxxxx31useEffect(() => {2 fetchData(params);3}, [{ id: 1 }]);✔️
xxxxxxxxxx11const params = useMemo(() => ({ id: 1 }), []);Sometimes multiple sibling components fetch the same data.
Solution:
This way the data is fetched once and shared.
Tools like React Query automatically:
React Query example:
xxxxxxxxxx41const { data } = useQuery({2 queryKey: ["user", id],3 queryFn: fetchUser,4});Duplicate calls are automatically prevented.
Some developers accidentally remount a component repeatedly:
xxxxxxxxxx11{isOpen && <UserList />}If isOpen toggles repeatedly, the component unmounts and remounts, triggering useEffect again.
Fix:
理解上面的内容后,简要的回答:
First, I check the
useEffectdependency array — missing or unstable dependencies often cause repeated fetch calls. Then I make sure the fetch function is memoized withuseCallbackand any params withuseMemo, so they don’t change every render. If several components need the same data, I move the fetching logic into a global store, such as zustand, so the data is fetched once and reused everywhere. For even more control, I use libraries like React Query, which provide request deduplication, caching, and stale-while-revalidate behavior out of the box.
In short: stabilize dependencies, avoid remounting, and centralize or cache the data so it only gets fetched once.
可以这样想:think of this like you are travelling, you don't take your complete cupboard or wardrobe with you, you take neccessary some part of it in the travel.
Just like that, whenever you writing thousands of files in your app, you don't need to ship everything to the production.So you need to bundle it and organize it somehow so that it will take minimum size on the user's device and it will be efficient for the performance.
理解了上面的内容之后,简要回答:
I usually optimize bundle size by focusing on code splitting, dependency management, and build-time optimizations. First, I implement route-level code splitting using
React.lazyandSuspense, so the app only loads what’s needed for the current page. For heavier features, I use dynamic imports or component-level lazy loading to avoid shipping large chunks upfront. I also regularly analyze the bundle using tools like Webpack Bundle Analyzer or Vite’s analyze plugin to identify large or unused dependencies. If a library is too heavy, I try to replace it with a smaller alternative, remove unused code, or tree-shake it. On the build side, I make sure features like tree-shaking, minification, and dead-code elimination are enabled, and I avoid importing entire libraries when I only need specific modules. For assets, I optimize images, use*.svgcomponents instead of PNGs, and rely on CDNs where possible.
Overall, the approach is: split the code, trim unnecessary dependencies, enable build optimizations, and continuously analyze the bundle to keep it lean.
当面试官问large forms,他们想要看到你是否能够优化form、维持状态的能力。
这是Shubham Kulkarni | Coding and Interviews这位老师的回答要点。
1、use form management library like react hook form,
2、structure the form to logical steps or sections.
3、use controlled components,
4、use zod library to validate.
下面是chatgpt的回答:
✅ 1. Group related fields into logical sections
Don’t treat 20 fields as 20 individual states. Instead, think of your form as sections:
This gives you:
✔ Better UI/UX ✔ Smaller components ✔ Cleaner validation
Example structure:
xxxxxxxxxx51<FormProvider {methods}>2 <PersonalInfoSection />3 <ContactSection />4 <PreferencesSection />5</FormProvider>✅ 2. Use a form library — don’t manage 20 states manually
It handles:
For 20+ fields, RHF is definitely better than useState or even Formik.
Example:
xxxxxxxxxx41const methods = useForm({2 defaultValues: { 20 fields here },3 mode: "onSubmit",4});✅ 3. Store all form values in one place (not 20 states)
For large forms, do NOT use:
xxxxxxxxxx31const [name, setName] = useState('');2const [email, setEmail] = useState('');3Instead:
xxxxxxxxxx81const methods = useForm({2 defaultValues: {3 name: '',4 email: '',5 age: '',6 // ...7 }8});Or if using useState manually:
xxxxxxxxxx11const [form, setForm] = useState({});✅ 4. Extract repeating field logic into reusable components
If many fields share:
Create a component:
xxxxxxxxxx91function FormInput({ name, label, register, errors }) {2 return (3 <div>4 <label>{label}</label>5 <input {register(name)} />6 {errors[name] && <p>{errors[name].message}</p>}7 </div>8 );9}Now your form becomes:
xxxxxxxxxx31<FormInput name="email" label="Email" />2<FormInput name="phone" label="Phone" />3<FormInput name="age" label="Age" />✅ 5. Use zod to avoid manual checks
With 20+ fields, writing if (!name) error... everywhere becomes painful.
Use Zod:
xxxxxxxxxx61const schema = z.object({2 name: z.string().min(1),3 email: z.string().email(),4 phone: z.string().min(10),5 // 20 more...6});Integrate with RHF:
xxxxxxxxxx31const methods = useForm({2 resolver: zodResolver(schema)3});Now all validation is centralized and reusable.
✅ 6. Use dynamic generation (optional but powerful)
If fields share patterns, define them as config:
xxxxxxxxxx61const fieldConfig = [2 { name: "email", label: "Email" },3 { name: "phone", label: "Phone" },4 { name: "address", label: "Address" },5 // ...6];Render automatically:
xxxxxxxxxx31{fieldConfig.map((f) => (2 <FormInput key={f.name} {f} />3))}Great for 20–100 fields.
✅ 7. Split the form into tabs or steps if possible
For UX improvement:
Use a stepper or wizard.
Better:
✔ Less intimidating ✔ Faster rendering ✔ Validation per step
✅ 8. Memoize heavy components
For example, expensive dropdowns.
xxxxxxxxxx31const CountrySelect = memo(function CountrySelect() {2 return <select>...</select>;3});RHF already minimizes re-renders, but memoization helps further.
🧪 9. For huge forms → Use RHF + Suspense + lazy-loaded sections
If your sections are large:
xxxxxxxxxx11const WorkInfo = lazy(() => import("./WorkInfo"));Better performance for enterprise apps.
理解上面的内容之后,就可以背诵答案了:
When handling large forms with over 20 fields, my approach focuses on maintainability and performance.
First, I modularize the form by grouping related fields into logical sections or sub-components. This keeps the code clean and the UI user-friendly.
To manage state efficiently, I use React Hook Form instead of multiple
useStatehooks. This centralizes the form state and significantly reduces unnecessary re-renders, which is crucial for performance at this scale.For validation, I integrate Zod for schema-based, type-safe validation. This keeps all the rules in one place and ensures consistency. I also extract common patterns into reusable input components—and if the form is highly repetitive, I’ll drive the fields from a config object to minimize boilerplate.
Finally, if the UX feels overwhelmed, I’ll break the form into multi-step wizards or tabs.
In short, my strategy is: centralized state, schema-driven validation, and a modular component structure.
面试管想听到的要点就是:the theme state should be global, so whenever you toggle the theme at the global state, it will be auto reflected in the children level.
1、I will check whether the dark mode is user preference based or system based.
2、if it's system based.I will use prefers-color-scheme media query to detect system preference on first load.then set the theme mode.
3、if it's user mode, I often rely on react context or redux to set global state, and rely on CSS variables or tailwindcss to set dark mode.
简要回答:
To implement dark mode in a React app, I usually manage a theme state at the top level, using React Context or a global store like Zustand. I store the user’s preference in
localStorageso it persists across sessions, and I use a toggle button in the UI to switch between light and dark themes. For styling, I often rely on CSS variables or a utility framework like Tailwind CSS, which supports dark mode with thedark:variant. Components then automatically adapt by referencing the theme variables, and I can also add a class likedarkon the root element to switch the entire app.
In short, the approach is: store theme in global state, persist the preference, and style components using CSS variables or a framework that supports dark mode
XSS (Cross-Site Scripting)
Frontend apps
为什么会发生 XSS?
前端可以做什么?
可用工具 / 方法?
{userInput}dangerouslySetInnerHTML + DOMPurifyeval()To prevent XSS, I implement a defense-in-depth strategy, focusing on both safe coding patterns and environmental security.
First, I leverage React’s default escaping. Since React automatically escapes values in JSX, I treat it as the first line of defense. I strictly avoid
dangerouslySetInnerHTMLunless there’s a specific requirement. If it's unavoidable, I always pipe the content through DOMPurify to strip out malicious scripts.Second, for URL-based attacks, I sanitize dynamic links to prevent
javascript:protocol injection. I ensure any user-provided URLs are validated against a whitelist of safe protocols likehttp:orhttps:.Third, I advocate for a strong Content Security Policy (CSP). By configuring CSP headers to disallow
unsafe-inlinescripts and restricting script sources to trusted domains, we can stop XSS even if a vulnerability exists in the code.Finally, I ensure all third-party dependencies are regularly audited using tools like
npm auditto avoid indirect vulnerabilities.In summary, my approach is: Escape by default, Sanitize when necessary, and enforce CSP for environment-level security.
(为了防止 XSS,我采取了深度防御策略,重点关注安全编码模式和环境安全。)
(首先,我利用 React 的默认转义。JSX 会自动转义变量,这是第一道防线。我严格避免使用
dangerouslySetInnerHTML,除非有特殊需求;如果非用不可,我一定会用 DOMPurify 过滤掉恶意脚本。)(第二,针对基于 URL 的攻击,我会对动态链接进行清理,防止
javascript:伪协议注入。我会确保用户提供的 URL 符合http或https的白名单。)(第三,我主张使用强有力的 CSP(内容安全策略)。通过配置 CSP 响应头来禁用
unsafe-inline脚本并限制信任域名,即使代码中存在漏洞,也能阻止 XSS 执行。)(最后,我会定期使用
npm audit等工具审计第三方依赖,以避免间接漏洞。)(总而言之,我的方法是:默认转义、必要时清理、并强制执行 CSP 以实现环境级安全。)
1️⃣ 题目关键词
Offline/Online state
Gracefully
React
Service Workers
2️⃣ 拆解后的思路
你可以把题目拆解成三个小问题:
(A) 如何在 React 中检测在线/离线状态?
用浏览器 API:
xxxxxxxxxx31navigator.onLine // 当前是否在线2window.addEventListener('online', callback)3window.addEventListener('offline', callback)在 React 里可以用 useState + useEffect 或 Context 来存储全局状态。
UI 上可以显示提示:比如顶部横幅“你已经离线”。
(B) 如何用 Service Worker 支持离线功能?
Service Worker 可以缓存静态资源和 API 数据。
核心点:
推荐使用库:Workbox 或 PWA setup(Create React App 默认支持)。
(C) 如何在重新上线时同步数据?
3️⃣ 组合成回答框架
navigator.onLine + online/offline 事件老师的要点:
1、I will use navigator.onLine API provided by the browser, to detect network status. And listen to the online and offline events.
2、use service workers to cache critical assets and API responses.
To handle offline and online states in a React app, I usually combine browser APIs with Service Workers.
First, I detect network status using the
navigator.onLineproperty and listen to theonlineandofflineevents, updating a global state or React Context so components can react accordingly. For example, I can show a banner or toast to inform users they are offline.Second, I use a Service Worker—for example, via Workbox—to cache critical assets and API responses. This allows the app to function in offline mode and serve cached content.
For data syncing, when the user goes back online, I can implement background sync to send queued updates to the server.
In short, my approach is: listen to network events, update UI reactively, cache resources with a Service Worker, and optionally queue requests for later syncing to provide a seamless offline/online experience.
Production is not getting the latest update
Unless in incognito
为什么普通用户看到旧版本?
为什么隐身模式正常?
如何解决?
简要回答:
This is a classic cache invalidation issue. When a site works in incognito but not in a normal tab, it means the browser is stale-loading resources from the local cache.
To resolve this permanently, I focus on three layers:
First, I ensure the build pipeline uses Content Hashing. By appending a unique hash to each JS and CSS filename, we force the browser to download the new version because the URL has changed.
Second, I'll audit the HTTP Cache-Control headers, specifically for the
index.html. Since the HTML file is the entry point that references the hashed bundles, it must be set tono-cacheormax-age=0. This ensures the browser re-validates with the server every time to see if there's a newindex.html.Third, if the app uses a Service Worker, I'll implement an update and skip-waiting logic. I'll make sure the Service Worker detects the new version and prompts the user to refresh, or automatically takes over the page to clear the old cache.
In summary, the key is to ensure immediate invalidation of the entry file (HTML) while leveraging long-term caching for hashed assets."
(这是一个经典的缓存失效问题。如果无痕模式正常而普通模式不正常,说明浏览器正在从本地缓存中加载旧资源。)
(为了彻底解决这个问题,我会从三个层面入手:)
(第一,我会确保构建流水线使用了内容哈希。通过给每个 JS 和 CSS 文件名添加唯一的哈希值,由于 URL 变了,浏览器会被强制下载新版本。)
(第二,我会审计 HTTP 缓存头,特别是
index.html的配置。因为 HTML 是入口文件,它引用了带哈希的资源包,所以必须设置为no-cache或max-age=0。这能确保浏览器每次都向服务器验证是否有新的入口文件。)(第三,如果应用使用了 Service Worker,我会实现 update 和 skip-waiting 逻辑。确保 Service Worker 能检测到新版本并提示用户刷新,或者自动接管页面以清除旧缓存。)
(总而言之,核心在于确保入口文件(HTML)立即失效,同时利用哈希资源的长效缓存。)